using UnityEngine;
using Unity.Collections.LowLevel.Unsafe;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Collections;
#if UNITY_EDITOR
using System.Reflection;
#endif

public class DataStore : MonoBehaviour
{
	private static DataStore instance;
	private void Awake()
	{
		instance = this;
	}
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public static DataStore GetInstance()
	{
		return instance;
	}
	
	public enum PtrType
	{
		Undefined,
		Double,
		Float,
		Int,
		Int64,
		Bool,
		FixedString128
	}
	[System.Serializable]
	public struct AnimData
	{
		public string name;
		public PtrType type;
		public int offset;
		public int size;
	}
	
	public DataStruct data;
	public AnimData[] animData;
	
#if UNITY_EDITOR
	private void OnValidate()
	{
		if (Application.isPlaying)
			return;
		
		//get target object, component class, property name and animationcurve from the timeline
		FieldInfo[] fields = data.GetType().GetFields();
		List<AnimData> list = new List<AnimData>();
		PtrType type;
		for (int i=0; i<fields.Length; i++)
		{
			FieldInfo field = fields[i];
			int offset = UnsafeUtility.GetFieldOffset(field);
			
			System.Type ft = field.FieldType;
			if (ft == typeof(double))
				type = PtrType.Double;
			else if (ft == typeof(float))
				type = PtrType.Float;
			else if (ft == typeof(long))
				type = PtrType.Int64;
			else if (ft == typeof(bool))
				type = PtrType.Bool;
			else if (ft == typeof(FixedString128Bytes))
				type = PtrType.FixedString128;
			else
				type = PtrType.Int;		//for int and enum
			
			//store data for runtime use
			AnimData a = new AnimData();
			a.name = field.Name;
			a.type = type;
			a.offset = offset;
			list.Add(a);
		}
		
		animData = list.ToArray();
	}
#endif
	
	//TODO: this could be called at build time if we can ensure we can re-serialize all assets (but it's also a risk because then all assets need to be reserialized when the struct changes)
	public AnimData GetAnimData(string fieldName)
	{
		for(int i=0; i<animData.Length; i++)
		{
			if (animData[i].name == fieldName)
				return animData[i];
		}
		Debug.LogError($"offset not found for fieldName: {fieldName}");
		return default;
	}
	
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	private unsafe bool GetAddr(out byte* addr, int offset, int size)
	{
		void* address = UnsafeUtility.AddressOf(ref data);
		byte* addressAsByte = (byte*)address;
		
		//safety checks
		int maxOffset = sizeof(DataStruct) - size;
		if (offset > maxOffset)
		{
			Debug.LogError($"offset would result in reading outside of struct: max={maxOffset} offset={offset}");
			addr = default;
			return false;
		}
		
		addr = addressAsByte + offset;	//address of DataStore.data + offset of the field we want to change
		return true;
	}
	
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public unsafe double GetDouble(AnimData animData)
	{
		if (animData.type == PtrType.Double)
		{
			byte* addr;
			if (GetAddr(out addr, animData.offset, animData.size))
				return *(double*)addr;
		}
		else Debug.LogError($"GetDouble called for property {animData.name} which is {animData.type}");
		//fail
		return 0.0d;
	}
	
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public unsafe float GetFloat(AnimData animData)
	{
		if (animData.type == PtrType.Float)
		{
			byte* addr;
			if (GetAddr(out addr, animData.offset, animData.size))
				return *(float*)addr;
		}
		else Debug.LogError($"GetFloat called for property {animData.name} which is {animData.type}");
		//fail
		return 0.0f;
	}
	
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public unsafe int GetInt(AnimData animData)
	{
		if (animData.type == PtrType.Int)
		{
			byte* addr;
			if (GetAddr(out addr, animData.offset, animData.size))
				return *(int*)addr;
		}
		else Debug.LogError($"GetInt called for property {animData.name} which is {animData.type}");
		//fail
		return 0;
	}
	
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public unsafe long GetInt64(AnimData animData)
	{
		if (animData.type == PtrType.Int64)
		{
			byte* addr;
			if (GetAddr(out addr, animData.offset, animData.size))
				return *(long*)addr;
		}
		else Debug.LogError($"GetInt64 called for property {animData.name} which is {animData.type}");
		//fail
		return 0L;
	}
	
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public unsafe bool GetBool(AnimData animData)
	{
		if (animData.type == PtrType.Bool)
		{
			byte* addr;
			if (GetAddr(out addr, animData.offset, animData.size))
				return *(bool*)addr;
		}
		else Debug.LogError($"GetBool called for property {animData.name} which is {animData.type}");
		//fail
		return false;
	}
	
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public unsafe FixedString128Bytes GetFixedString128(AnimData animData)
	{
		if (animData.type == PtrType.FixedString128)
		{
			byte* addr;
			if (GetAddr(out addr, animData.offset, animData.size))
				return *(FixedString128Bytes*)addr;
		}
		else Debug.LogError($"GetFixedString128 called for property {animData.name} which is {animData.type}");
		//fail
		return default;
	}
}